#include "LockManager.h"
#include "TimeoutManager.h"
#include "LoggerModule/Logger.h"
#include <csignal>
#include <cassert>
#include <cerrno>


/*
 * Timeout handling thread function
 */
void* timeoutThreadFunc(void *arg)
{
	LockManager *lock_manager = (LockManager *)arg;	
	sigset_t mask;
	siginfo_t sigInfo;
	
	// we are interested in TIMEOUT_SIG
	sigemptyset(&mask);
	sigaddset(&mask, TIMEOUT_SIG);
		
	while(1)
	{		
		// Wait for TIMEOUT_SIG, get info
		if(sigwaitinfo(&mask, &sigInfo) < 0)
		{
			Logger::getInstance().logDlm(Logger::ERRNO,
				"[LOCK MANAGER] Error while waiting for signal in timeout handling thread");
			pthread_exit(NULL);										
		}
		
		// inform lock manager about timeout		
		lock_manager->timedOut(sigInfo.si_value.sival_int);	
	}

	return NULL;
}


LockManager::LockManager(DlmComm *cm) :
	dlmComm_(cm)
{
	pthread_mutex_init(&mutex_, NULL);
}


LockManager::~LockManager()
{
	for(Resources::iterator iter = resources_.begin(); iter != resources_.end(); ++iter)
			delete iter->second;
			
	pthread_mutex_destroy(&mutex_);			
}


void LockManager::run()
{
	int rval;	
	
	Request *request = NULL;	
	pthread_t timeoutThread;
	
	if((rval = pthread_create(&timeoutThread, NULL, timeoutThreadFunc, NULL)) != 0)
	{
		errno = rval;
		Logger::getInstance().logDlm(Logger::ERRNO, "[LOCK MANAGER] Unable to create thread handling timeouts");
		return;
	}


	while(true)
	{
		if((request = dlmComm_->receiveRequest()) == NULL)
			continue;

		pthread_mutex_lock(&mutex_);
		switch(request->code_)
		{
			case DLM_LOCK:
				processRequest((LockRequest *)request);
				break;
			case DLM_UNLOCK:
				processRequest((UnlockRequest *)request);
				handleWaiting(); // after unlocking there is a chance some other process can obtain his lock
				break;
			case DLM_TRYLOCK:
				processRequest((TryLockRequest *)request);
				break;
			default:
				assert(0); // this should be recognized in dlmComm and shouldn't happen here
		}	
		pthread_mutex_lock(&mutex_);			

		delete request;				
	}
}



void LockManager::timedOut(pid_t pid)
{
	Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Timeout for process with pid %d", pid);	
	
	dlmComm_->sendResponse(pid, DLM_FAILED);

	pthread_mutex_lock(&mutex_);
	{
		for(Resources::iterator iter = resources_.begin(); iter != resources_.end(); ++iter)
				iter->second->removeWaiting(pid);
		timeoutManager_.timeoutHandled(pid);
	}
	pthread_mutex_unlock(&mutex_);		

}


void LockManager::exited(pid_t pid)
{
	dlmComm_->clientUnregister(pid);
	
	pthread_mutex_lock(&mutex_);
	{	
		for(Resources::iterator iter = resources_.begin(); iter != resources_.end(); ++iter)
				if(iter->second->removeWaiting(pid))
					Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Lock on resource %d owned by process pid %d released", iter->first, pid);					
				
		handleWaiting();
	}
	pthread_mutex_unlock(&mutex_);	
}


void LockManager::processRequest(const LockRequest *request)
{
	Logger::getInstance().logDlm(Logger::INFO, 
		"[LOCK MANAGER] Pid %d requested %s lock on resource %d",
		request->pid_, lockStr((LockType)request->lockType_), request->resourceId_);
	
	Resources::iterator rit = resources_.find(request->resourceId_);
	
	if(rit == resources_.end())
	{
		resources_[request->resourceId_] = new Resource(request->resourceId_, request->pid_, (LockType)request->lockType_);
		dlmComm_->sendResponse(request->pid_, DLM_SUCCESS);
		
		Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d got %s lock on resource %d", 
									request->pid_, lockStr((LockType)request->lockType_), request->resourceId_);
	}
	else if(rit->second->addOwner(request->pid_, (LockType)request->lockType_))
	{
		dlmComm_->sendResponse(request->pid_, DLM_SUCCESS);	
		
		Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d got %s lock on resource %d", 
									request->pid_, lockStr((LockType)request->lockType_), request->resourceId_);
	}
	else 
	{
		// already added to waiting queue when called addOwner in previous if clause
		if(request->timeout_ > 0)
		{
			if(timeoutManager_.launchTimer(request->pid_, request->timeout_))
			{					
				Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d waiting for %s lock on resource %d with timeout %d", 
					request->pid_, lockStr((LockType)request->lockType_), request->resourceId_, request->timeout_);
			}
			else
			{
				// as for some reason we failed in launching timer, we're sending "FAILED" response to unblock waiting process
				dlmComm_->sendResponse(request->pid_, DLM_FAILED);			
			}
		}
		else
		{
			Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d waiting for %s lock on resource %d without timeout", 
										request->pid_, lockStr((LockType)request->lockType_), request->resourceId_);
		}
	}
}


void LockManager::processRequest(const TryLockRequest *request)
{
	Logger::getInstance().logDlm(Logger::INFO, 
		"[LOCK MANAGER] Pid %d requested %s trylock on resource %d",
		request->pid_, lockStr((LockType)request->lockType_), request->resourceId_);
		
	Resources::iterator rit = resources_.find(request->resourceId_);
	
	if(rit == resources_.end())
	{
		dlmComm_->sendResponse(request->pid_, DLM_SUCCESS);
		
		Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d could get %s lock on resource %d", 
									request->pid_, lockStr((LockType)request->lockType_), request->resourceId_);
	}
	else if(rit->second->canAddOwner(request->pid_, (LockType)request->lockType_))
	{
		Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d could get %s lock on resource %d", 
									request->pid_, lockStr((LockType)request->lockType_), request->resourceId_);	
	}
	else
	{
		Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d couldn't get %s lock on resource %d without blocking", 
									request->pid_, lockStr((LockType)request->lockType_), request->resourceId_);
									
		dlmComm_->sendResponse(request->pid_, DLM_FAILED);
	}
}


void LockManager::processRequest(const UnlockRequest *request)
{
	Resources::iterator rit = resources_.find(request->resourceId_);

	Logger::getInstance().logDlm(Logger::INFO, 
		"[LOCK MANAGER] Pid %d requested unlocking resource %d",
		request->pid_, request->resourceId_);	
	
	if(rit == resources_.end())
	{
		Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d tried to unlock a not-existing resource %d", request->pid_, request->resourceId_);			
		dlmComm_->sendResponse(request->pid_, DLM_FAILED);
	}
	else if(rit->second->removeOwner(request->pid_))
	{
		Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d has succesfuly unlocked resource %d", request->pid_, request->resourceId_);			
		dlmComm_->sendResponse(request->pid_, DLM_SUCCESS);		
	}
	else 
	{
		Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d tried to unlock resource %d which is not locked by him", request->pid_, request->resourceId_);					
		dlmComm_->sendResponse(request->pid_, DLM_FAILED);
	}
}


void LockManager::handleWaiting()
{
	pid_t pid;

	for(Resources::iterator iter = resources_.begin(); iter != resources_.end(); iter++)
		while ((pid = (*iter).second->update()) != -1)
		{
			Logger::getInstance().logDlm(Logger::INFO, "[LOCK MANAGER] Pid %d got lock for resource %d", pid, iter->first);					
			dlmComm_->sendResponse(pid, DLM_SUCCESS);
		}
}

